/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.talkback.labeling;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.text.TextUtils;
import android.util.Log;
import com.android.utils.LogUtils;
import com.android.utils.StringBuilderUtils;
import com.android.utils.labeling.Label;
import com.android.utils.labeling.LabelProviderClient;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class DataConsistencyCheckRequest extends LabelClientRequest<List<Label>> {

    private Context mContext;
    private OnDataConsistencyCheckCallback mCallback;

    public DataConsistencyCheckRequest(LabelProviderClient client, Context context,
                                       OnDataConsistencyCheckCallback callback) {
        super(client);
        mContext = context;
        mCallback = callback;
    }

    @Override
    public List<Label> doInBackground() {
        final List<Label> allLabels = mClient.getCurrentLabels();

        if ((allLabels == null) || allLabels.isEmpty()) {
            return null;
        }

        final PackageManager pm = mContext.getPackageManager();

        final List<Label> candidates = new ArrayList<>(allLabels);
        ListIterator<Label> i = candidates.listIterator();

        // Iterate through the labels database, and prune labels that belong
        // to valid packages.
        while (i.hasNext()) {
            final Label l = i.next();

            // Ensure the label has a matching installed package.
            final String packageName = l.getPackageName();
            PackageInfo packageInfo;
            try {
                packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
            } catch (PackageManager.NameNotFoundException e) {
                // If there's no installed package, leave the label in the
                // list for removal.
                LogUtils.log(DataConsistencyCheckRequest.class, Log.VERBOSE,
                        "Consistency check removing label for unknown package %s.",
                        packageName);
                continue;
            }

            // Ensure the signature hash of the application matches
            // the hash of the package when the label was stored.
            final String expectedHash = l.getPackageSignature();
            final String actualHash = computePackageSignatureHash(packageInfo);
            if (TextUtils.isEmpty(expectedHash) || TextUtils.isEmpty(actualHash)
                    || !expectedHash.equals(actualHash)) {
                // If the expected or actual signature hashes aren't
                // valid, or they don't match, leave the label in the list
                // for removal.
                LogUtils.log(DataConsistencyCheckRequest.class, Log.WARN,
                        "Consistency check removing label due to signature mismatch " +
                                "for package %s.",
                        packageName);
                continue;
            }

            // If the label has passed all consistency checks, prune the
            // label from the list of potentials for removal.
            i.remove();
        }

        return candidates; // now containing only labels for removal
    }

    @Override
    public void onPostExecute(List<Label> labelsToRemove) {
        if (mCallback != null) {
            mCallback.onConsistencyCheckCompleted(labelsToRemove);
        }
    }

    private static String computePackageSignatureHash(PackageInfo packageInfo) {
        String signatureHash = "";

        final Signature[] sigs = packageInfo.signatures;
        try {
            final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
            for (Signature s : sigs) {
                messageDigest.update(s.toByteArray());
            }

            signatureHash = StringBuilderUtils.bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            LogUtils.log(DataConsistencyCheckRequest.class, Log.WARN,
                    "Unable to create SHA-1 MessageDigest");
        }

        return signatureHash;
    }

    public interface OnDataConsistencyCheckCallback {
        public void onConsistencyCheckCompleted(List<Label> labelsToRemove);
    }
}
